package org.fullhappy.dao;

import java.lang.reflect.Field;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.RollbackException;
import javax.persistence.Table;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;
import javax.persistence.Query;
//import org.eclipse.persistence.exceptions.DatabaseException;
import org.fullhappy.emf.EMF;
import org.fullhappy.exception.NotFoundDatabaseException;

/**
 *
 * @author caohonghiep
 */
public abstract class DAO<T> {

    private Class<T> entityClass;

    public DAO(Class<T> entityClass) {
        this.entityClass = entityClass;
    }
    private EntityManager em = EMF.get().createEntityManager();//Persistence.createEntityManagerFactory("fhpPU").createEntityManager();

    protected EntityManager getEntityManager() {
        if (!em.isOpen()) {
            em = EMF.get().createEntityManager();//Persistence.createEntityManagerFactory("fhpPU").createEntityManager();
        }
        return em;
    }

    public T create(T entity) throws Exception {
        EntityTransaction txn = getEntityManager().getTransaction();
        txn.begin();
        try {
            getEntityManager().persist(entity);
            txn.commit();
            return entity;
        } catch (RollbackException e) {
            Throwable t = e.getCause();
//            if (t != null && t instanceof DatabaseException) {
//                throw (DatabaseException) t;
//            }
            throw e;
        } catch (Exception e) {
            throw e;
        } finally {
            if (txn.isActive()) {
                txn.rollback();
            }
            //getEntityManager().close();
        }
    }

    public void edit(T entity) throws Exception {
        EntityTransaction txn = getEntityManager().getTransaction();
        txn.begin();
        try{
            getEntityManager().merge(entity);
            //getEntityManager().flush();
            txn.commit();
        }catch(Exception e){
            throw e;
        } finally {
            if (txn.isActive()) {
                txn.rollback();
            }
            //getEntityManager().close();
        }
    }

    public void remove(T entity) {
        getEntityManager().remove(getEntityManager().merge(entity));
    }

    public T find(Object id) {
        return getEntityManager().find(entityClass, id);
    }

    public List<T> findAllExpectDeleted() {
        Query query = getEntityManager().createQuery(
                "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e.status != '0'");
        return query.getResultList();
    }

    public List<T> findAll() {
        CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass)).where();
        return getEntityManager().createQuery(cq).getResultList();
    }

    public List<T> findRange(int[] range) {
        CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        q.setMaxResults(range[1] - range[0]);
        q.setFirstResult(range[0]);
        return q.getResultList();
    }

    public List<T> getObjectValue(Class resultClazz, String attributeName) {

        CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
        CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(resultClazz);
        Root<T> from = criteriaQuery.from(entityClass);

        criteriaQuery.select(from.get(attributeName));
        criteriaQuery.where(criteriaBuilder.equal(from.get("aaa"), "aaa"));
//        List<>
        return null;
    }

    public int count() {
        CriteriaQuery cq = getEntityManager().getCriteriaBuilder().createQuery();
        Root<T> rt = cq.from(entityClass);
        cq.select(getEntityManager().getCriteriaBuilder().count(rt));
        javax.persistence.Query q = getEntityManager().createQuery(cq);
        return ((Long) q.getSingleResult()).intValue();
    }

    public boolean isExisted(Object id) {
        T t = find(id);
        if (t == null) {
            return false;
        }
        return true;
    }

    public List<T> findSame(T entity) throws NotFoundDatabaseException {
        String tableName = entityClass.getAnnotation(Table.class).name();
        String columnName = null;
        Class fieldClass = null;
        Object fieldValue = null;
        String columnIdName = null;
        String fieldIdName = null;
        Object idValue = null;
        Query query = null;
        String stringQuery = " FROM " + tableName + " WHERE 1 = 1 ";
        Field[] fields = entityClass.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);

            fieldClass = field.getType();
            if (field.isAnnotationPresent(Column.class)) {
                columnName = ((Column) field.getAnnotation(Column.class)).name();
            } else if (field.isAnnotationPresent(JoinColumn.class)) {
                columnName = ((JoinColumn) field.getAnnotation(JoinColumn.class)).name();
            } else {
                continue;
            }

            if (field.isAnnotationPresent(Id.class)) {
                stringQuery = "SELECT " + columnName + stringQuery;
                columnIdName = columnName;
                fieldIdName = field.getName();
            }

            try {
                fieldValue = field.get(entity);
            } catch (IllegalArgumentException ex) {
                Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            }
            if (fieldValue == null) {
                continue;
            } else {
                if (fieldClass.isAnnotationPresent(Table.class)) {
                    fieldValue = getIdValue(fieldValue);
                    if (fieldValue == null) {
                        continue;
                    }
                }
                if (fieldValue instanceof Date) {
                    fieldValue = (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")).format(fieldValue);
                }
                stringQuery = stringQuery + " AND " + columnName + " = '" + fieldValue + "'";
            }
        }
        query = getEntityManager().createNativeQuery(stringQuery);


        List<T> result = query.getResultList();
        if (result == null || result.isEmpty()) {
            throw new NotFoundDatabaseException();
        }
        stringQuery = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e." + fieldIdName + " IN (";
        for (Object object : result) {
            stringQuery = stringQuery + object + ",";
        }
        stringQuery = stringQuery.substring(0, stringQuery.length() - 1) + ")";
        query = getEntityManager().createQuery(stringQuery);
        return query.getResultList();
//        result = query.getResultList();
//        if (result == null || result.isEmpty()) {
//            throw new NotFoundDatabaseException();
//        }
//        return result;
    }

    private Object getIdValue(Object value) {
        Field[] fields = value.getClass().getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            if (field.isAnnotationPresent(Id.class)) {
                try {
                    return field.get(value);
                } catch (IllegalArgumentException ex) {
                    Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
                } catch (IllegalAccessException ex) {
                    Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        return null;
    }

    public String getCurrentValuesHaveBeenChanged(T entity) {
        T currentEntity = find(getIdValue(entity));
        Field[] fields = entityClass.getDeclaredFields();
        Object fieldValue = null;
        Object currentFieldValue = null;
        String xmlForCurrentValuesHaveBeenChanged = "";
        String columnName = null;
        for (Field field : fields) {
            field.setAccessible(true);

            if (field.isAnnotationPresent(Column.class)) {
                columnName = ((Column) field.getAnnotation(Column.class)).name();
            } else if (field.isAnnotationPresent(JoinColumn.class)) {
                columnName = ((JoinColumn) field.getAnnotation(JoinColumn.class)).name();
            } else {
                continue;
            }
            
            try {
                fieldValue = field.get(entity);
            } catch (IllegalArgumentException ex) {
                Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            }

            try {
                currentFieldValue = field.get(currentEntity);
            } catch (IllegalArgumentException ex) {
                Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IllegalAccessException ex) {
                Logger.getLogger(DAO.class.getName()).log(Level.SEVERE, null, ex);
            }

            if (field.getType().isAnnotationPresent(Table.class)) {
                fieldValue = getIdValue(fieldValue);
                currentFieldValue = getIdValue(currentFieldValue);
            }
            
            if(fieldValue.equals(currentFieldValue)){
               continue; 
            }else{
                xmlForCurrentValuesHaveBeenChanged += "<"+columnName+">"+currentFieldValue+"</"+columnName+">";
            }
        }
        return xmlForCurrentValuesHaveBeenChanged;
    }
    
    public List<T> find(String stringQuery) throws NotFoundDatabaseException{
        Query query = getEntityManager().createQuery(stringQuery);
        List<T> result = query.getResultList();
        if (result == null || result.isEmpty()) {
            throw new NotFoundDatabaseException();
        }
        return result;
    }
    
    
}
